package org.cunframework.sc.map4j.support.handle.map;

import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Map;

import org.cunframework.modules.utils.FileUtil;
import org.cunframework.modules.utils.JSONHelper;
import org.cunframework.modules.utils.asynchttp.ResponseCallback;
import org.cunframework.modules.utils.asynchttp.utility.ResponseCallbackTrace;
import org.cunframework.sc.map4j.core.GMaps;
import org.cunframework.sc.map4j.core.GPoint;
import org.cunframework.sc.map4j.core.PointLatLng;
import org.cunframework.sc.map4j.core.RectLatLng;
import org.cunframework.sc.map4j.core.internals.DownloadInfo;
import org.cunframework.sc.map4j.core.internals.DownloadStatus;
import org.cunframework.sc.map4j.core.internals.LevelStatus;
import org.cunframework.sc.map4j.core.internals.TileStatus;
import org.cunframework.sc.map4j.core.providers.GMapProvider;
import org.cunframework.sc.map4j.core.providers.GMapProviders;
import org.cunframework.sc.map4j.util.FileUtils;
import org.cunframework.sc.map4j.util.MapUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;

import io.undertow.websockets.WebSocketConnectionCallback;
import io.undertow.websockets.core.BufferedTextMessage;
import io.undertow.websockets.core.AbstractReceiveListener;
import io.undertow.websockets.core.WebSocketChannel;
import io.undertow.websockets.core.WebSockets;
import io.undertow.websockets.spi.WebSocketHttpExchange;

/**
 * Map4J 瓦片下载入品
 * 
 * <p/>
 * <p>User: weiq
 * <p>Date: 2015年11月5日 下午5:42:41 
 * <p>Version: 1.0
 */
public class MapDownloadsHandler implements WebSocketConnectionCallback{
    
    private static final Logger LOGGER = LoggerFactory.getLogger(MapDownloadsHandler.class);
    
    public final ResponseCallbackTrace callbackTrace = new ResponseCallbackTrace();
    
    protected final ResponseCallback binCallback = new WebsocketsResponseHandler() {

        public void onSubmit(URL url, TileStatus ts, WebSocketChannel channel) {
            LOGGER.info(String.format("开始下载瓦片, URL:%s", url.toString()));
        }
        
        public void onResponse(byte[] data, URL url, TileStatus ts, WebSocketChannel channel, GPoint point) {
            ts.isDown(true);
            int currentZoom = ts.getLs().getLevel();
            ts.getLs().getDs().getProvider().CacheImage(currentZoom, point, data);
            WebSockets.sendText(ts.toString(), channel, null);
            
            //如果全部下载完成返回结果
            isDone(ts, channel);
            
            LOGGER.info(String.format("瓦片下载完成, 级别: %d, %d/%d,  数据长度:%d, URL: %s",
                    currentZoom,
                    ts.getLs().getDownCount(),
                    ts.getLs().getCount(),
                    data.length,url.toString()));
        }

        /**
         * 处理下载失败
         * 失败后把下载失败的图片URL进行记录. 当全部瓦片下载完成后告知前台,
         * 由用户决定是否再次下载的图片.
         * @param url
         */
        public void onDownloadFail(URL url, TileStatus ts, WebSocketChannel channel) {
            ts.isDown(false);
            isDone(ts, channel);
            
            //写入失败日志
            GMapProvider provider = ts.getLs().getDs().getProvider();
            
            String path = GMaps.Instance.tilePath+"/"+
                    provider.getName() + "/downfail.m4j";
            
            String cachePath = provider.getCachePath(ts.getLs().getLevel(), ts.getPoint());
            FileUtils.logDownFail(path, cachePath, url.toString());
            LOGGER.info(String.format("下载失败, URL: %s", url.toString()));
        }
        
        /**
         * 是否下载完成
         * @return
         */
        private boolean isDone(TileStatus ts, WebSocketChannel channel){
            DownloadStatus ds = ts.getLs().getDs();
            if(ds.isDone()){
                ds.downCount();
                
                String msg = String.format("全部瓦片下载完成! 总共:%d, 成功:%d, 失败:%d",
                        ds.getCount(),ds.downCount(),ds.failCount());
                LOGGER.info(msg);
                Map sendMap = Maps.newHashMap();
                sendMap.put("isError", false);
                sendMap.put("isDone", true);
                sendMap.put("status", ds.toJSON());
                WebSockets.sendText(JSONHelper.toJSONString(sendMap), channel, null);
                return true;
            }
            return false;
        }
    };
    
    @Override
    public void onConnect(WebSocketHttpExchange exchange, WebSocketChannel channel) {
        
        channel.getReceiveSetter().set(new AbstractReceiveListener() {
            @Override
            protected void onFullTextMessage(WebSocketChannel channel, BufferedTextMessage message) {
                String dataStr = message.getData();
                try{
                    DownloadInfo di = JSONHelper.json2Object(dataStr, DownloadInfo.class);
                    if("all".equals(di.getDownType()))
                        this.downloadTile(channel, di);
                    else if("fail".equals(di.getDownType()))
                        this.downloadFailTile(channel, di);
                }catch(Exception e){
                    LOGGER.error("发生错误 : ", e);
                    Map sendMap = Maps.newHashMap();
                    sendMap.put("isError", true);
                    sendMap.put("content", "发生错误 : " + e.getMessage() + ",详细原因请查看日志");
                    
                    WebSockets.sendText(JSONHelper.toJSONString(sendMap), channel, null);
                }
            }
            
            private void downloadFailTile(WebSocketChannel channel, DownloadInfo di){
                String providerName = di.getProviderName();
                GMapProvider provider = GMapProviders.Instance.tryGetProvider(providerName);
                String filePath = GMaps.Instance.tilePath+"/"+
                        provider.getName() + "/downfail.m4j";
                List<String> fails = FileUtils.readAndDeleteFailList(filePath);
                int count = fails.size();
                int downCount = 0;
                int failCount = 0;
                for(String fail : fails){
                    String[] temp = fail.split(";");
                    String path = temp[0];
                    String url = temp[1];
                    try {
                        provider.CacheImage(path, url);
                        downCount++;
                        LOGGER.info("失败列表中的 url:"+url+"下载成功,从文件中移出");
                    } catch (IOException e) {
                        FileUtils.logDownFail(filePath, path, url);
                        failCount++;
                        LOGGER.error("失败列表中的 url:"+url+"下载失败",e);
                    }
                }
                
                DownloadStatus ds = new DownloadStatus(count, downCount, failCount);
                Map sendMap = Maps.newHashMap();
                sendMap.put("isError", false);
                sendMap.put("isDone", true);
                sendMap.put("repeat", di.getFailRepeat()+1);
                sendMap.put("status", ds.toJSON());
                WebSockets.sendText(JSONHelper.toJSONString(sendMap), channel, null);
            }
            
            private void downloadTile(WebSocketChannel channel, DownloadInfo di){
                List<PointLatLng> pointLatLngs = di.getLatLngs();
                
                if(pointLatLngs==null){
                    LOGGER.error("未取得多边形的范围,点集为空或者不能组成多边形");
                    return;
                }

                int minZoom = di.getFromZoom();
                int maxZoom = di.getToZoom();
                int currentZoom = minZoom;
                String providerName = di.getProviderName(); //"GoogleChinaMap";
                
                RectLatLng rect = MapUtil.getBounds(pointLatLngs);
                
                GMapProvider provider = GMapProviders.Instance.tryGetProvider(providerName);
                if(provider==null){
                    LOGGER.error("provider name : "+providerName+" is null !");
                    Map sendMap = Maps.newHashMap();
                    sendMap.put("isError", true);
                    sendMap.put("content", "provider name : "+providerName+" is null !");
                    
                    WebSockets.sendText(JSONHelper.toJSONString(sendMap), channel, null);
                }else{
                    //ThreadWaiter.reset();
                    //收集下载信息
                    int jumpCount = 0;
                    int totalCount = 0;
                    DownloadStatus ds = new DownloadStatus(provider);
                    while(currentZoom <= maxZoom){
                        List<GPoint> points = provider.getProjection().GetAreaTileList(rect, currentZoom, 0);
                        int len = points.size();
                        totalCount += len;
                        LevelStatus ls = new LevelStatus(currentZoom, ds);
                        for(int i=0; i<len; i++){
                            //加入前检查一下这个图片是否存在
                            String cachePath = provider.getCachePath(currentZoom, points.get(i));
                            if(FileUtil.isExist(cachePath)){
                                LOGGER.info("跳过,因为瓦片存在,"+cachePath);
                                jumpCount++;
                                continue;
                            }
                            String url = provider.GetTileImageUrl(points.get(i), currentZoom);
                            TileStatus ts = new TileStatus(ls,points.get(i),url);
                            ls.addTileStatus(ts);
                        }
                        ds.addLevelStatus(ls);
                        currentZoom++;
                    }
                    
                    //如果这个区域的瓦片全部跳过,即已经存在.
                    if(jumpCount == totalCount){
                        Map sendMap = Maps.newHashMap();
                        sendMap.put("isError", true);
                        sendMap.put("title", "已下载");
                        sendMap.put("content", "此范围无需再次下载!");
                        WebSockets.sendText(JSONHelper.toJSONString(sendMap), channel, null);
                        return;
                    }
                    
                    //开始循环下载
                    if(ds.getTileCount()==0){
                        Map sendMap = Maps.newHashMap();
                        sendMap.put("isError", true);
                        sendMap.put("content", "待下载数量为0 期望值>0 !");
                        WebSockets.sendText(JSONHelper.toJSONString(sendMap), channel, null);
                        return;
                    }
                    
                    List<LevelStatus> lss = ds.getLss();
                    for(LevelStatus ls : lss){
                        List<TileStatus> tss = ls.getTss();
                        for(TileStatus ts : tss){
                            GMaps.http.get(ts.getUrl(), callbackTrace.trace(binCallback), ts, channel, ts.getPoint());
                        }
                    }
                    //ThreadWaiter.waitingThreads();
                }
            }
        });
        channel.resumeReceives();
    }
}
